#include "FileSystem.h"


FileSystem::FileSystem(): diskServices () {
	blockSize = diskServices.getBlockSize();
}

FileSystem::FileSystem(int diskSizeInBytes, int dataBlockSize, int numberOfInodes):
	diskServices (diskSizeInBytes, dataBlockSize, numberOfInodes),blockSize(dataBlockSize) {}

FileSystem::~FileSystem() {}

int FileSystem::createFile(string flag) {
	int fileInode = diskServices.allocateInode();
	if (fileInode != -1) {
		if (flag.compare("regular")==0) {
			//the default is a normal file
		}
		if (flag.compare("soft-link")==0) {
			diskServices.setInodeType(fileInode,'s');
		}
	}
	return fileInode;
}

int FileSystem::createDir () {
	int dirInode = diskServices.allocateInode();
	diskServices.setInodeType(dirInode,'d');
	return dirInode;
}

char FileSystem::getFileType(int inode) {
	return diskServices.getInodeType(inode);
}

int FileSystem::getFileSize(int inode) {
	return diskServices.getFileSize(inode);
}

int FileSystem::f_read (int inode, unsigned char *buffer, int offset, int nBytes) {
	bool firstIteration = true;
	int globalIndex=0;
	int offsetInsideInode = offset/blockSize;
	int offsetInsideBlock = offset%blockSize;
	int numOfCurrDataBlock = diskServices.getDataBlock(inode,offsetInsideInode);
	unsigned char localBuffer[blockSize];

	if(numOfCurrDataBlock == -1)
		globalIndex--;

	while (numOfCurrDataBlock != -1 && globalIndex<nBytes) {
		diskServices.readBlock(numOfCurrDataBlock,localBuffer);
		int i=0;
		if (firstIteration) {
			while (i+offsetInsideBlock<blockSize && globalIndex<nBytes
				&& (int)strlen((char*)localBuffer) > i+offsetInsideBlock) {
					buffer[globalIndex] = localBuffer[i+offsetInsideBlock];
					globalIndex++;
					i++;
			}
			firstIteration = false;
		} else {
			while (i<blockSize && globalIndex<nBytes
					&& (int)strlen((char*)localBuffer) > i) {
				buffer[globalIndex] = localBuffer[i];
				globalIndex++;
				i++;
			}
		}
		offsetInsideInode++;
		numOfCurrDataBlock = diskServices.getDataBlock(inode,offsetInsideInode);
	}
	buffer[globalIndex] = 0;

	return globalIndex;
}

int FileSystem::f_write (int inode, unsigned char *buffer, int offset, int nBytes) {
	int globalIndex=0;
	int offsetInsideInode = offset/blockSize;
	int offsetInsideBlock = offset%blockSize;
	//read the first block that contains the offset and write back the block with the new information
	int numOfCurrDataBlock = diskServices.getDataBlock(inode,offsetInsideInode);
	unsigned char localBuffer[blockSize];

	if (numOfCurrDataBlock == -1) {
		numOfCurrDataBlock = diskServices.allocateDataBlock();
		if(numOfCurrDataBlock==-1) {
			cerr << "Not enough disk space for writing" << endl;
			return -1;
		}
		diskServices.setDataBlock(inode, offsetInsideInode, numOfCurrDataBlock);
	}

	diskServices.readBlock(numOfCurrDataBlock,localBuffer);


	int i=0;

	while((globalIndex<nBytes) && (i+offsetInsideBlock<blockSize)) {
		localBuffer[i+offsetInsideBlock] = buffer[globalIndex];
		globalIndex++;
		i++;
	}

	/*
	//fill the rest of the local buffer with nulls
	while (i<blockSize) {
		localBuffer[i] = '\0';
		i++;
	}
	*/

	diskServices.writeBlock(numOfCurrDataBlock,localBuffer);

	while (globalIndex<nBytes) {
		offsetInsideInode++;
		i=0;

		while (i<blockSize && globalIndex<nBytes) {
			localBuffer[i] = buffer[globalIndex];
			globalIndex++;
			i++;
		}

		/*
		//fill the rest of the local buffer with nulls
		while (i<blockSize) {
			localBuffer[i] = '\0';
			i++;
		}
		*/

		numOfCurrDataBlock = diskServices.getDataBlock(inode,offsetInsideInode);
		if (numOfCurrDataBlock == -1) {
			numOfCurrDataBlock = diskServices.allocateDataBlock();
			if(numOfCurrDataBlock==-1) {
				cerr << "Not enough disk space for writing" << endl;
				return -1;
			}
			diskServices.setDataBlock(inode, offsetInsideInode, numOfCurrDataBlock);
		}

		diskServices.writeBlock(numOfCurrDataBlock, localBuffer);
	}
	//if the file after update is bigger than the file before update, set the new size
	if (diskServices.getFileSize(inode) < (offset+globalIndex)) {
		diskServices.setFileSize(inode,offset+globalIndex);
	}
	return globalIndex;
}



vector<DirectoryEntry*> FileSystem::d_read (int inode) {
	if (diskServices.getInodeType(inode) != 'd') {
		cerr << "the given inode doesn't represent a directory!!!" << endl;
		std::exit(-1);
	}
	vector<DirectoryEntry*> dirContent;
	dirContent.clear();
	int dirSize = diskServices.getFileSize(inode);
	//DirectoryEntry size is 16 byte
	int i=0;
	int blockNum = diskServices.getDataBlock(inode,i);
	while (dirSize>=16 && blockNum!=-1) {
		//there are still free blocks for this inode & there are still 16 bytes to read
		unsigned char blockContent[blockSize];
		diskServices.readBlock(blockNum,blockContent);
		//cout << "blockContent: " << blockContent << endl;
		int bytesLeftToReadFromBlock = blockSize;
		int internalOffset=0;
		while (bytesLeftToReadFromBlock >= 16 && dirSize>=16) {
			int iNodeNum;
			char fileName[12];
			iNodeNum = getInodeNumFromBuffer(blockContent,internalOffset*16);
			getFileNameFromBuffer(blockContent,fileName,internalOffset*16);

			DirectoryEntry* de = new DirectoryEntry(iNodeNum,fileName);
			dirContent.push_back(de);
			bytesLeftToReadFromBlock-=16;
			dirSize-=16;
			internalOffset++;
		}

		blockNum = diskServices.getDataBlock(inode,++i);
	}
	return dirContent;

}


void FileSystem::d_write (int inode, vector<DirectoryEntry*> filesList) {

	if(diskServices.getInodeType(inode) != 'd') {
		cerr << "the given inode doesn't represent a directory!!!" << endl;
		std::exit(-1);
	}
	//clear all the data written inside the directory
	vector<DirectoryEntry*>::iterator it;
	diskServices.resetInode(inode);
	int blockNum, i=0, totalNumOfBytes=0;
	it = filesList.begin();
	while (it!=filesList.end() && (blockNum = diskServices.allocateDataBlock()) != -1) {
		//there are still free blocks in file sys && there are still DirectoryEntries to write
		int writtenByesInBlock = 0;
		unsigned char directoryEntries[blockSize];
		resetBlockContent(directoryEntries);
		while (it!=filesList.end()/*fileListCounter<(int)filesList.size()*/ && writtenByesInBlock<=blockSize-16) {
			char directoryEntry[16];
			(*it)->parseIntoCharArr(directoryEntry);
			for (int i=0; i<16; i++) {
				directoryEntries[i+writtenByesInBlock] = directoryEntry[i];
			}
			writtenByesInBlock+=16;
			it++;

		}
		//cout << "directoryEntries: " << directoryEntries << endl;
		diskServices.setDataBlock(inode,i,blockNum);
		diskServices.writeBlock(blockNum,directoryEntries);
		//accumulates the total number of bytes  written to a specific folder (inode)
		totalNumOfBytes+=writtenByesInBlock;
		i++;
	}
	//since we override the data in the dir, the new size is the actual one
	diskServices.setFileSize(inode,totalNumOfBytes);
}

void FileSystem::f_delete (int inode) {
	diskServices.freeInode(inode);
}

void FileSystem::d_delete (int inode) {
	if (diskServices.getFileSize(inode)==32) {//contains only the default directories "." and ".."
		diskServices.freeInode(inode);
	} else {
		cerr << "the directory is not empty" << endl;
	}
}

void FileSystem::incInodeHardLinks(int inode) {
	diskServices.incHardLinksNum(inode);
}

void FileSystem::decInodeHardLinks(int inode) {
	diskServices.decHardLinksNum(inode);
}

int FileSystem::getInodeNumFromBuffer(const unsigned char* buffer, int offset) {
	/*
	cout << *(buffer + offset + 3) << endl;
	cout << *(buffer + offset + 2) << endl;
	cout << *(buffer + offset + 1) << endl;
	cout << *(buffer + offset) << endl;
	 */
	return (int)(*(buffer + offset + 3) << 24 |
			     *(buffer + offset + 2) << 16 |
			     *(buffer + offset + 1) << 8 |
			     *(buffer+offset));
}

void FileSystem::getFileNameFromBuffer(const unsigned char* buffer,char* fileName, int offset) {
	for (int i=0; i<12; i++) {
		fileName[i] = buffer[offset+4+i];
	}
}

void FileSystem::resetBlockContent(unsigned char* buffer) {
	for (int i=0; i<blockSize; i++) {
		buffer[i]=0;
	}
}

void FileSystem::main() {


	int dirInodeNum = createDir();
	char fileName1[] = "file1";
	char fileName2[] = "file2";
	char fileName3[] = "file3";
	char fileName4[] = "file4";
	char fileName5[] = "file5";
	char fileName6[] = "file6";
	char fileName7[] = "file7";

	DirectoryEntry* de1 = new DirectoryEntry(10,fileName1);
	DirectoryEntry* de2 = new DirectoryEntry(20,fileName2);
	DirectoryEntry* de3 = new DirectoryEntry(300,fileName3);
	DirectoryEntry* de4 = new DirectoryEntry(4000,fileName4);
	DirectoryEntry* de5 = new DirectoryEntry(5000,fileName5);
	DirectoryEntry* de6 = new DirectoryEntry(6000,fileName6);
	DirectoryEntry* de7 = new DirectoryEntry(20000,fileName7);

	vector<DirectoryEntry*> input;
	input.push_back(de1);
	input.push_back(de2);
	input.push_back(de3);
	input.push_back(de4);
	input.push_back(de5);
	input.push_back(de6);
	input.push_back(de7);

	d_write(dirInodeNum,input);

	//delete (de1);
	//delete (de2);
	//delete (de3);
	//delete (de4);
	//delete (de5);
	//delete (de6);
	//delete (de7);
	//	input.clear();

	vector<DirectoryEntry*> output(d_read(dirInodeNum));
	vector<DirectoryEntry*>::iterator it;
	for (it=output.begin(); it!=output.end(); it++) {
		cout << "InodeNum: " << (*it)->getINodeNum() << endl;
		cout << "FileName: " << (*it)->getFileName() << endl;
	}
	/*int fileInodeNum;
	unsigned char content[] = "1234567890123456789012345678901234567890123456789012345678901234567890";
	//for (int i=0; i<23; i++) {
	fileInodeNum = createFile("regular");
	if (fileInodeNum != -1) {
		f_write(fileInodeNum,content,60,70);
	}
	//}*/
	int fileInodeNum;
	unsigned char content[] = "1234567890";
	int inodesAdr[8];
	for (int i=0; i<8; i++) {
		fileInodeNum = createFile("regular");
		inodesAdr[i] = fileInodeNum;
		if (fileInodeNum != -1) {
			f_write(fileInodeNum,content,0,10);
		}
	}

	f_delete(inodesAdr[2]);
	f_delete(inodesAdr[3]);

	int nBytes = 5;
	unsigned char fileContent[nBytes];
	int ret = f_read(inodesAdr[4],fileContent,0,nBytes);

	if(ret != -1)
		cout<<"file content(" << ret << "): "<<fileContent<<endl;
	else
		cout<<"no content!!"<<endl;

	/*
	fileInodeNum = createFile("regular");
	unsigned char content1[] = "09876543210987654321";
	f_write(fileInodeNum,content1,0,20);


	nBytes = 20;
	unsigned char fileContent1[nBytes];
	f_read(fileInodeNum,fileContent1,0,nBytes);
	cout<<"file content: "<<fileContent1<<endl;
*/
	cout<<"Bye Bye!"<<endl;

}
